/** @file    Table.cpp
 *  @author  Alessandro Polo
 *  @version $Id: Table.cpp 2833 2010-08-02 23:12:57Z alex $
 *  @brief
 * File containing methods for the wosh::Table class.
 * The header for this class can be found in Table.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <core/Table.h>
 #include <core/ObjectAllocator.h>
 #include <core/Utilities.h>
 #include <core/List.h>
 #include <core/Variant.h>


using namespace std;
namespace wosh {

 WOSH_REGISTER(wosh::Table, "wosh::ISerializable", 1.01, _static_Table)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

Table::Table() {
	this->rows = 0;
	this->cols = 0;
	this->items = NULL;
	this->headerVertical = NULL;
	this->headerHorizontal = NULL;
	this->table_name = "Table";
 }

Table::Table( unsigned int row_count, unsigned int col_count, bool horizontalHeader, bool verticalHeader ) {
	this->rows = 0;
	this->cols = 0;
	this->items = NULL;
	this->headerVertical = NULL;
	this->headerHorizontal = NULL;
	this->table_name = "Table";
	alloc(row_count, col_count, horizontalHeader, verticalHeader);
 }

Table::Table( const Table& m ) {
	this->rows = 0;
	this->cols = 0;
	this->items = NULL;
	this->headerVertical = NULL;
	this->headerHorizontal = NULL;
	*this = m;
 }

Table::~Table() {
	clear(true);
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Table::alloc( unsigned int row_count, unsigned int col_count, bool horizontalHeader, bool verticalHeader ) {
	if ( this->items != NULL ) return WRET_ERR_INTERNAL;

	this->rows = row_count;
	this->cols = col_count;
	unsigned int count = this->rows * this->cols;
	if ( count > 0 ) {
		this->items = new ISerializable*[count];
		for(unsigned int i=0; i<count; i++) {
			this->items[i] = NULL;
		 }
	 }
	allocHeaderHorizontal(horizontalHeader);
	allocHeaderVertical(verticalHeader);
	return WRET_OK;
 }

void Table::allocHeaderHorizontal( bool value ) {
	if ( this->headerHorizontal != NULL ) {
		for(unsigned int i=0; i<this->cols; i++) {
			if ( this->headerHorizontal[i] == NULL ) continue;
			delete this->headerHorizontal[i]; this->headerHorizontal[i] = NULL;
		 }
		 delete [] this->headerHorizontal; this->headerHorizontal = NULL;
	 }
	if (!value)
		return;
	this->headerHorizontal = new ISerializable*[this->cols];
	for(unsigned int i=0; i<this->cols; i++) {
		this->headerHorizontal[i] = NULL;
	 }
 }

void Table::allocHeaderVertical( bool value ) {
	if ( this->headerVertical != NULL ) {
		for(unsigned int i=0; i<this->rows; i++) {
			if ( this->headerVertical[i] == NULL ) continue;
			delete this->headerVertical[i]; this->headerVertical[i] = NULL;
		 }
		 delete [] this->headerVertical; this->headerVertical = NULL;
	 }
	if (!value)
		return;
	this->headerVertical = new ISerializable*[this->rows];
	for(unsigned int i=0; i<this->rows; i++) {
		this->headerVertical[i] = NULL;
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Table::isEmpty() const {
	if ( this->items == NULL ) return true;
	unsigned int count = this->rows * this->cols;
	if ( count == 0 ) return true;
	for(unsigned int i=0; i<count; i++) {
		if ( this->items[i] != NULL )
			return false;
	 }
	return true;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Table::clear( bool deleteContents ) {
	allocHeaderVertical(false);
	allocHeaderHorizontal(false);
	clearContents(deleteContents);
	delete [] this->items; this->items = NULL;
	this->rows = 0;
	this->cols = 0;
 }

void Table::clearContents( bool deleteContents ) {
	if ( this->items == NULL ) return;
	unsigned int count = this->rows * this->cols;
	for(unsigned int i=0; i<count; i++) {
		if ( this->items[i] == NULL ) continue;
		if ( deleteContents )
			delete this->items[i];
		this->items[i] = NULL;
	 }
	//delete [] this->items; this->items = NULL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS

WRESULT Table::set( ISerializable* object, unsigned int row, unsigned int col, bool deletePrevious ) {
	if ( this->items == NULL ) return WRET_ERR_PARAM;
	if ( row >= this->rows ) return WRET_ERR_PARAM;
	if ( col >= this->cols ) return WRET_ERR_PARAM;
	unsigned int raster_index = row * this->cols + col;
	return setAt(object, raster_index, deletePrevious );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Table::setAt( ISerializable* object, unsigned int raster_index, bool deletePrevious ) {
	if ( this->items == NULL ) return WRET_ERR_PARAM;
	unsigned int count = this->rows * this->cols;
	if ( raster_index >= count ) return WRET_ERR_PARAM;
	if ( deletePrevious && this->items[raster_index] != NULL )
		delete this->items[raster_index];
	this->items[raster_index] = object;
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Table::setColumn( const List* list, unsigned int col, unsigned int row_start, bool acquireItem, bool deletePrevious ) {
	if ( col >= this->cols ) return WRET_ERR_PARAM;
	if ( row_start >= this->rows ) return WRET_ERR_PARAM;
	if ( list == NULL ) return WRET_ERR_PARAM;
	unsigned int iRow = row_start;
	List::ConstIterator it;
	for ( it=list->begin(); it!=list->end(); ++it, ++iRow ) {
		if ( *it == NULL ) {
			set(NULL, iRow, col, deletePrevious);
			continue;
		 }
		if ( acquireItem )
			set(*it, iRow, col, deletePrevious);
		else 
			set((*it)->clone(), iRow, col, deletePrevious);
	 }
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Table::setHeaderHorizontal( ISerializable* object, unsigned int col, bool deletePrevious ) {
	if ( this->headerHorizontal == NULL ) return WRET_ERR_PARAM;
	if ( col >= this->cols ) return WRET_ERR_PARAM;
	if ( deletePrevious && this->headerHorizontal[col] != NULL )
		delete this->headerHorizontal[col];
	this->headerHorizontal[col] = object;
	return WRET_OK;
 }

WRESULT Table::setHeaderVertical( ISerializable* object, unsigned int row, bool deletePrevious ) {
	if ( this->headerVertical == NULL ) return WRET_ERR_PARAM;
	if ( row >= this->rows ) return WRET_ERR_PARAM;
	if ( deletePrevious && this->headerVertical[row] != NULL )
		delete this->headerVertical[row];
	this->headerVertical[row] = object;
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
int Table::mergeByColumn( const Table* other, int primary_col, short compare_mode, short overwrite_mode ) {
	if ( other == NULL ) return -1;
	if ( other->cols != this->cols ) return -1;
	for (unsigned int row=0; row<other->rows; row++) {
		const ISerializable* other_obj = other->items[row * this->cols + primary_col];
//		if ( other_obj == other ) return other;
		
	 }
	return -2;
 }
*/
///////////////////////////////////////////////////////////////////////////////////////////////////// SETTERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// GETTERS

const Variant& Table::getAsVariant( unsigned int row, unsigned int col ) const {
	const ISerializable* obj = getAt( row * this->cols + col );
	if ( obj == NULL || !obj->isKindOf<Variant>() ) return Variant::EmptyVariant;
	const Variant* vObj = obj->as<Variant>();
	if ( vObj == NULL ) return Variant::EmptyVariant;
	return *vObj;
 }

ISerializable* Table::getAt( unsigned int raster_index ) {
	if ( this->items == NULL ) return NULL;
	unsigned int count = this->rows * this->cols;
	if ( raster_index >= count ) return NULL;
	return this->items[raster_index];
 }
const ISerializable* Table::getAt( unsigned int raster_index ) const {
	if ( this->items == NULL ) return NULL;
	unsigned int count = this->rows * this->cols;
	if ( raster_index >= count ) return NULL;
	return this->items[raster_index];
 }

ISerializable* Table::getHeaderHorizontal( unsigned int col ) {
	if ( this->headerHorizontal == NULL ) return NULL;
	if ( col >= this->cols ) return NULL;
	return this->headerHorizontal[col];
 }
const ISerializable* Table::getHeaderHorizontal( unsigned int col ) const {
	if ( this->headerHorizontal == NULL ) return NULL;
	if ( col >= this->cols ) return NULL;
	return this->headerHorizontal[col];
 }

ISerializable* Table::getHeaderVertical( unsigned int row ) {
	if ( this->headerVertical == NULL ) return NULL;
	if ( row >= this->rows ) return NULL;
	return this->headerVertical[row];
 }
const ISerializable* Table::getHeaderVertical( unsigned int row ) const {
	if ( this->headerVertical == NULL ) return NULL;
	if ( row >= this->rows ) return NULL;
	return this->headerVertical[row];
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////// OPERATORS

Table& Table::operator=(const Table& m) {
	if (this == &m) return *this; // same object?

	this->table_name = m.table_name;
	clear(true);
	alloc( m.rows, m.cols );
	unsigned int count = this->rows * this->cols;
	for(unsigned int i=0; i<count; i++) {
		if ( m.items[i] == NULL ) continue;
		this->items[i] = m.items[i]->clone();
	 }
	if ( m.hasHeaderHorizontal() ) {
		allocHeaderHorizontal(true);
		for(unsigned int i=0; i<this->cols; i++) {
			if ( m.headerHorizontal[i] == NULL ) continue;
			this->headerHorizontal[i] = m.headerHorizontal[i]->clone();
		 }
	 }
	if ( m.hasHeaderVertical() ) {
		allocHeaderVertical(true);
		for(unsigned int i=0; i<this->rows; i++) {
			if ( m.headerVertical[i] == NULL ) continue;
			this->headerVertical[i] = m.headerVertical[i]->clone();
		 }
	 }
	return *this;
 }

bool Table::operator==(const Table &other) const {
	if ( this->rows != other.rows ) return false;
	if ( this->cols != other.cols ) return false;
	if ( this->table_name != other.table_name ) return false;
	unsigned int count = this->rows * this->cols;
	for(unsigned int i=0; i<count; i++) {
		if ( (this->items[i] == NULL) ^ (other.items[i] == NULL) ) continue;
		if ( this->items[i] == NULL ) continue;
		if ( this->items[i]->compare(other.items[i]) == WOSH_COMPARE_RET_NOT_EQUAL )
			return false;
	 }
	return true;
 }

/////////////////////////////////////////////////////////////////////////////////////////////////// OPERATORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////// SEARCH

int Table::findRowOfItem( int col, const ISerializable* other, short compare_mode ) const {
	if ( col == -2 ) { // any row
		int ret = findRowOfItem(-1, other, compare_mode);
		if ( ret > -2 ) return ret;
		return -2;
	 }
	else if ( col == -1 ) { // horizontal header
		if ( this->headerVertical == NULL ) return -2;
		for (unsigned int row=0; row<this->rows; row++) {
			if ( this->headerVertical[row] == other ) return row;
			if ( this->headerVertical[row] == NULL ) continue;
			if ( this->headerVertical[row]->compare(other) != compare_mode ) continue;
			return row;
		 }
		return -1;
	 }
	else if ( col >= 0 && col < (int)this->cols ) { // selected row
		if ( this->items == NULL ) return -2;
		for (unsigned int row=0; row<this->rows; row++) {
			const ISerializable* iobj = this->items[row * this->cols + col];
			if ( iobj == other ) return row;
			if ( iobj == NULL ) continue;
			if ( iobj->compare(other) != compare_mode ) continue;
			return row;
		 }
		return -1;
	 }
	return -1;
 }

int Table::findColumnOfItem( int row, const ISerializable* other, short compare_mode ) const {
	if ( row == -2 ) { // any row
		int ret = findColumnOfItem(-1, other, compare_mode);
		if ( ret > -2 ) return ret;
		return -2;
	 }
	else if ( row == -1 ) { // horizontal header
		if ( this->headerHorizontal == NULL ) return -2;
		for (unsigned int col=0; col<this->cols; col++) {
			if ( this->headerHorizontal[col] == other ) return col;
			if ( this->headerHorizontal[col] == NULL ) continue;
			if ( this->headerHorizontal[col]->compare(other) != compare_mode ) continue;
			return col;
		 }
		return -1;
	 }
	else if ( row > -2 && row < (int)this->rows ) { // selected row
		if ( this->items == NULL ) return -2;
		for (unsigned int col=0; col<this->cols; col++) {
			const ISerializable* iobj = this->items[row * this->cols + col];
			if ( iobj == other ) return col;
			if ( iobj == NULL ) continue;
			if ( iobj->compare(other) != compare_mode ) continue;
			return col;
		 }
		return -1;
	 }
	return -1;
 }


const ISerializable* Table::findMappedColumnItem( unsigned int col_primary, unsigned int return_column, const ISerializable* other, short compare_mode ) const {
	if ( this->items == NULL || col_primary >= this->cols || return_column >= this->cols  ) return NULL;
	unsigned int row_start = 0;
	unsigned int row_end = this->rows;
	for (unsigned int row=row_start; row<row_end; row++) {
		const ISerializable* iobj = this->items[row * this->cols + col_primary];
		if ( iobj == other ) return other;
		if ( iobj == NULL ) continue;
		if ( iobj->compare(other) != compare_mode ) continue;
		iobj = this->items[row * this->cols + return_column];
		return iobj;
	 }
	return NULL;
 }

////////////////////////////////////////////////////////////////////////////////////////////////////// SEARCH
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// SORTING

WRESULT Table::swapRow( unsigned int source_index, unsigned int dest_index ) {
	if ( source_index >= this->rows ) return WRET_ERR_PARAM;
	if ( dest_index >= this->rows ) return WRET_ERR_PARAM;
	if ( source_index == dest_index ) return WRET_OK;
	// backup old row
	ISerializable** tempRow = new ISerializable*[this->cols];
	ISerializable* tempVHeader = NULL;
	for(unsigned int i=0; i<this->cols; i++) {
		tempRow[i] = this->items[dest_index * this->cols + i];
	 }
	if ( this->headerVertical != NULL )
		tempVHeader = this->headerVertical[dest_index];
	// overwrite pointers of old row
	for(unsigned int i=0; i<this->cols; i++) {
		this->items[dest_index * this->cols + i] = this->items[source_index * this->cols + i];
	 }
	if ( this->headerVertical != NULL )
		this->headerVertical[dest_index] = this->headerVertical[source_index];
	// restore the cached row
	for(unsigned int i=0; i<this->cols; i++) {
		this->items[source_index * this->cols + i] =tempRow[i];
	 }
	if ( this->headerVertical != NULL )
		this->headerVertical[source_index] = tempVHeader;
	delete [] tempRow;
	return WRET_OK;
 }

WRESULT Table::sortByColumn( int column, short order ) {
	if ( column == -1 && this->headerVertical == NULL ) return WRET_ERR_PARAM;
	if ( column < 0 || column >= (int)this->cols ) return WRET_ERR_PARAM;
	// bubble sort
	int row = this->rows+1;
	while( --row > 0 ) {
		for( int i=0; i<row-1; ++i ) {
			ISerializable* obj1 = this->items[i * this->cols + column];
			ISerializable* obj2 = this->items[(i+1) * this->cols + column];
			if ( obj1 == NULL && obj2 == NULL )
				continue;
			if ( obj1 == NULL ) {
				if ( order == 1 )
					swapRow(i+1, i); // move forward
				continue;
			 }
			if ( obj2 == NULL ) {
				if ( order == 0 )
					swapRow(i+1, i); // move forward
				continue;
			 }
			short comparison = obj1->compare(obj2);
			if ( comparison == 0 )
				continue;
			else if ( (comparison < 0) ^ (order == 1) )
				swapRow(i+1, i); // move forward
		 }
	 }

	return WRET_OK;
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// SORTING
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// HELPERS

std::string Table::getClassNameOf( unsigned int row, unsigned int col ) const {
	const ISerializable* obj = get( row, col );
	if ( obj == NULL ) return "";
	return obj->getClassName();
 }

std::string Table::getClassNameOfCol( unsigned int col ) const {
	std::string classname = "";
	for(unsigned int row=0; row<this->rows; row++) {
		const ISerializable* obj = get( row, col );
		if ( obj == NULL ) return "";
		if ( classname.size() == 0 ) {
			classname = obj->getClassName();
			continue;
		 }
		if ( obj->getClassName() != classname )
			return "";
	 }
	return classname;
 }

std::string Table::getClassNameOfItems() const {
	std::string classname = "";
	unsigned int count = this->rows * this->cols;
	for(unsigned int i=0; i<count; i++) {
		if ( this->items[i] == NULL ) continue;
		if ( classname.size() == 0 ) {
			classname = this->items[i]->getClassName();
			continue;
		 }
		if ( this->items[i]->getClassName() != classname )
			return "";
	 }
	return classname;
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// HELPERS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

std::string Table::toString() const {
	return Utilities::format("%s[%s;%d,%d]", getClassNameOnlyOf(getClassName()), this->table_name.c_str(), this->rows, this->cols );
 }

bool Table::updateHash( DataHashContext* hash_context ) const {
	if ( hash_context == NULL ) return false;
	bool ret = true;
	ret = hash_context->update( this->table_name );
	ret = hash_context->update( this->rows ) & ret;
	ret = hash_context->update( this->cols ) & ret;
	unsigned int count = this->rows * this->cols;
	for(unsigned int i=0; i<count; i++) {
		if ( this->items[i] == NULL ) continue;
		ret = this->items[i]->updateHash( hash_context ) & ret;
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
